/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * @author Vladimir N. Molotkov, Stepan M. Mishura
 */

package org.apache.harmony.security.asn1;

/**
 * Encodes ASN.1 types with BER (X.690)
 * 
 * @see http://asn1.elibel.tm.fr/en/standards/index.htm
 */

public class BerOutputStream {

	/**
	 * Encoded byte array
	 */
	public byte[] encoded;

	/**
	 * current offset
	 */
	protected int offset;

	/**
	 * Current encoded length
	 */
	public int length;

	/**
	 * Current encoded content
	 */
	public Object content;

	public BerOutputStream() {
	}

	public void encodeANY() {
		System.arraycopy(content, 0, encoded, offset, length);
		offset += length;
	}

	public void encodeBitString() {
		// FIXME check encoding
		final BitString bStr = (BitString) content;
		encoded[offset] = (byte) bStr.unusedBits;
		System.arraycopy(bStr.bytes, 0, encoded, offset + 1, length - 1);
		offset += length;
	}

	public void encodeBoolean() {
		if (((Boolean) content).booleanValue()) {
			encoded[offset] = (byte) 0xFF;
		} else {
			encoded[offset] = 0x00;
		}
		offset++;
	}

	public void encodeChoice(ASN1Choice choice) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void encodeExplicit(ASN1Explicit explicit) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void encodeGeneralizedTime() {
		System.arraycopy(content, 0, encoded, offset, length);
		offset += length;
	}

	public void encodeInteger() {
		System.arraycopy(content, 0, encoded, offset, length);
		offset += length;
	}

	public void encodeOctetString() {
		System.arraycopy(content, 0, encoded, offset, length);
		offset += length;
	}

	public void encodeOID() {

		final int[] oid = (int[]) content;

		int oidLen = length;

		// all subidentifiers except first
		int elem;
		for (int i = oid.length - 1; i > 1; i--, oidLen--) {
			elem = oid[i];
			if (elem > 127) {
				encoded[offset + oidLen - 1] = (byte) (elem & 0x7F);
				elem = elem >> 7;
				for (; elem > 0;) {
					oidLen--;
					encoded[offset + oidLen - 1] = (byte) (elem | 0x80);
					elem = elem >> 7;
				}
			} else {
				encoded[offset + oidLen - 1] = (byte) elem;
			}
		}

		// first subidentifier
		elem = oid[0] * 40 + oid[1];
		if (elem > 127) {
			encoded[offset + oidLen - 1] = (byte) (elem & 0x7F);
			elem = elem >> 7;
			for (; elem > 0;) {
				oidLen--;
				encoded[offset + oidLen - 1] = (byte) (elem | 0x80);
				elem = elem >> 7;
			}
		} else {
			encoded[offset + oidLen - 1] = (byte) elem;
		}

		offset += length;
	}

	public void encodeSequence(ASN1Sequence sequence) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void encodeSequenceOf(ASN1SequenceOf sequenceOf) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void encodeSet(ASN1Set set) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void encodeSetOf(ASN1SetOf setOf) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void encodeString() {
		System.arraycopy(content, 0, encoded, offset, length);
		offset += length;
	}

	public final void encodeTag(int tag) {

		encoded[offset++] = (byte) tag; // FIXME long form?

		if (length > 127) { // long form
			int eLen = length >> 8;
			byte numOctets = 1;
			for (; eLen > 0; eLen = eLen >> 8) {
				numOctets++;
			}

			encoded[offset] = (byte) (numOctets | 0x80);
			offset++;

			eLen = length;
			final int numOffset = offset + numOctets - 1;
			for (int i = 0; i < numOctets; i++, eLen = eLen >> 8) {
				encoded[numOffset - i] = (byte) eLen; // FIXME long value?
			}
			offset += numOctets;
		} else { // short form
			encoded[offset++] = (byte) length;
		}
	}

	public void encodeUTCTime() {
		System.arraycopy(content, 0, encoded, offset, length);
		offset += length;
	}

	/*
	 * LENGTH
	 */

	public void getChoiceLength(ASN1Choice choice) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void getExplicitLength(ASN1Explicit sequence) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void getSequenceLength(ASN1Sequence sequence) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void getSequenceOfLength(ASN1SequenceOf sequence) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void getSetLength(ASN1Set set) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public void getSetOfLength(ASN1SetOf setOf) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}

	public int getStringLength(Object object) {
		throw new RuntimeException("Is not implemented yet"); // FIXME
	}
}